﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using Microsoft.Samples.ServiceHosting.StorageClient;
using Microsoft.ServiceHosting.ServiceRuntime;
using System.Collections.ObjectModel;

using BusinessLogic.Tools;


namespace ZeCloud.Resto55.Business.Table
{
    public class RestoService : TableStorageDataServiceContext
    {
        public const string ANONYMOUS = "ANONYMOUS";

        public static long TICKS_AVANT_LA_BAISSE = 0;


        public RestoService(StorageAccountInfo accountInfo, RetryPolicy in_policy)
            : base(accountInfo)
        {
            RetryPolicy = in_policy;
        }

        public IQueryable<Resto> Restos
        {
            get
            {
                return this.CreateQuery<Resto>("Restos");
            }
        }

        public IQueryable<Element> Elements
        {
            get
            {
                return this.CreateQuery<Element>("Elements");
            }
        }

        public IQueryable<DetailPrix> DetailPrix
        {
            get
            {
                return this.CreateQuery<DetailPrix>("DetailPrix");
            }
        }

        public IQueryable<RestoIndex> RestoIndexes
        {
            get
            {
                return this.CreateQuery<RestoIndex>("RestoIndexes");
            }
        }

        public Resto CreerRestoAnonyme(string in_nom, int in_dep, string in_ville)
        {
            return CreerRestoPar(ANONYMOUS, in_nom, in_dep, in_ville);
        }

        public Resto CreerRestoPar(string in_idCreator, string in_nom, int in_dep, string in_ville)
        {
            // Vérification du département
            if ((in_dep == 0) || (in_dep >= 100))
            {
                RoleManager.WriteToLog("Information", "RestoService, CreerRestoPar : Valeur incorrecte pour département");
                return null;
            }

            // Attention à ce que la ville ne contienne pas de _ !
            if (in_ville.Contains("_"))
            {
                RoleManager.WriteToLog("Information", "RestoService, CreerRestoPar : La ville ne peut pas contenir de _");
                return null;
            }

            try
            {
                var l_created = new Resto(in_idCreator, in_nom, in_dep * 1000, in_ville);

                // Vérifier que le resto n'existe pas déjà
                var l_alreadyinResto = GetRestoParNomEtVille(in_nom, in_ville);
                if (l_alreadyinResto != null)
                {
                    RoleManager.WriteToLog("Warning", "RestoService, CreerRestoPar : le resto existe déjà - " + in_nom + " dans la ville  de " + in_ville);
                    return l_alreadyinResto;
                }

                this.AddObject("Restos", l_created);
                this.SaveChanges();

                CreerEchantillons(l_created);
                CreerIndex(l_created);
                return l_created;
            }
            catch (DataServiceRequestException dsre)
            {
                //var log = log4net.LogManager.GetLogger(this.GetType());
                //log.Debug("AnniversaireService, CreerAnniversaire : Entry already exists !");
                RoleManager.WriteToLog("Information", "RestoService, CreerResto : Entry already exists !");

                return null;
            }
            catch (InvalidOperationException ioe)
            {
                // Pb d'implementation, on réutilise un contexte existant
                //var log = log4net.LogManager.GetLogger("Metier.AnniversaireService");
                //log.Debug("AnniversaireService, CreerAnniversaire : Pb d'implémentation !");
                RoleManager.WriteToLog("Information", "RestoService, CreerResto : Pb d'implémentation !");

                return null;
            }
        }


        public Resto CreerRestoPar(string in_idCreator, string in_nom, string in_adr1, string in_adr2, int in_codePostal, string in_ville, string in_siteWeb, string in_tel)
        {
            // Extraction du département
            if ((in_codePostal < 1) || (in_codePostal >= 100000))
            {
                RoleManager.WriteToLog("Information", "RestoService, CreerRestoPar : Le code postal est incorrect");
                return null;
            }

            // Attention à ce que la ville ne contienne pas de _ !
            if (in_ville.Contains("_"))
            {
                RoleManager.WriteToLog("Information", "RestoService, CreerRestoPar : La ville ne peut pas contenir de _");
                return null;
            }

            int dep;
            if (in_codePostal < 10000)
            {
                dep = Int32.Parse("0" + in_codePostal.ToString().Substring(0, 1));
            }
            else
            {
                dep = Int32.Parse(in_codePostal.ToString().Substring(0, 2));
            }

            try
            {
                var l_created = new Resto(in_idCreator, in_nom, in_codePostal, in_ville);
                l_created.site = in_siteWeb;
                l_created.adr1 = in_adr1;
                l_created.adr2 = in_adr2;
                l_created.tel = in_tel;
                l_created.dep = dep;

                // Vérifier que le resto n'existe pas déjà
                // [PENDING] Gérer le cas particulier de 2 restos qui auraient le même nom 
                //          dans des villes de mêmes noms mais de codes postaux différents !!!
                var l_alreadyinResto = GetRestoParNomEtVille(in_nom, in_ville);
                if (l_alreadyinResto != null)
                {
                    RoleManager.WriteToLog("Warning", "RestoService, CreerRestoPar : le resto existe déjà - " + in_nom + " dans la ville  de " + in_ville);
                    return l_alreadyinResto;
                }

                this.AddObject("Restos", l_created);
                this.SaveChanges();

                // Création des templates situés dans le menu
                CreerEchantillons(l_created);
                CreerIndex(l_created);

                return l_created;
            }
            catch (DataServiceRequestException dsre)
            {
                //var log = log4net.LogManager.GetLogger(this.GetType());
                //log.Debug("AnniversaireService, CreerAnniversaire : Entry already exists !");
                RoleManager.WriteToLog("Information", "RestoService, CreerResto : Entry already exists !");

                return null;
            }
            catch (InvalidOperationException ioe)
            {
                // Pb d'implementation, on réutilise un contexte existant
                //var log = log4net.LogManager.GetLogger("Metier.AnniversaireService");
                //log.Debug("AnniversaireService, CreerAnniversaire : Pb d'implémentation !");
                RoleManager.WriteToLog("Information", "RestoService, CreerResto : Pb d'implémentation !");

                return null;
            }
        }

        // Créer un index pour le restaurant
        public void CreerIndex(Resto resto)
        {
            string nom = StringHelper.RemoveAccentuation(resto.nom + " " + resto.ville+ " " + resto.uniqueId);
            IEnumerable<string> mots = StringHelper.SplitWords(nom).Distinct();

            var indexes = from m in mots
                          select new RestoIndex(nom, m, resto);
            foreach (RestoIndex index in indexes)
            {
                try
                {
                    this.AddObject("RestoIndexes", index);
                    this.SaveChanges();
                }
                catch (DataServiceRequestException dsre)
                {
                    RoleManager.WriteToLog("Information", "RestoService, Creer Index : " + dsre.Message + " !");
                }
            }
        }

        // Attention : cette méthode supprime et recréer l'indexation sur tous les restaurants ! Ca peut vite devenir long...
        public void RebuilRestoIndex()
        {
            foreach (RestoIndex ri in RestoIndexes)
            {
                this.DeleteObject(ri);
                this.SaveChanges();
            }
            foreach (Resto r in Restos)
                CreerIndex(r);
        }

        // Recherche un resto sur n'importe quel mot indexé (actuellement nom et ville, voir CreerIndex)
        public IEnumerable<Resto> RechercherRestosParMots(string recherche)
        {
            List<string> mots = StringHelper.SplitWords(recherche).ToList();

            // Récupère le mot le plus long
            string mot = "";
            foreach (string m in mots)
            {
                if (m.Length > mot.Length)
                    mot = m;
            }
            mots.Remove(mot);

            List<RestoIndex> liste = (from ri in RestoIndexes
                                      where ri.PartitionKey == mot
                                      select ri).ToList();

            IEnumerable<Resto> orderedResults;

            if (mots.Count > 0)
            {
                // Si plusieurs mots dans la recherche, vérifie qu'il sont tous présents
                List<Resto> resultats = new List<Resto>();
                foreach (RestoIndex index in liste)
                {
                    bool found = true;
                    for (int j = 0; j < mots.Count() && found; j++)
                        found = index.ValeurIndexe.Contains(mots[j]);

                    if (found) resultats.Add(new Resto { PartitionKey = index.RestoPartitionKey, RowKey = index.RestoRowKey, nom = index.NomRestaurant, ville = index.VilleRestaurant, uniqueId = index.UniqueId });
                }
                orderedResults = from r in resultats
                                 orderby r.nom
                                 select r;
            }
            else
            {
                // Un seul mot dans la recherche, va 
                orderedResults = from ri in liste
                                 orderby ri.NomRestaurant, ri.VilleRestaurant
                                 select new Resto { PartitionKey = ri.RestoPartitionKey, RowKey = ri.RestoRowKey, nom = ri.NomRestaurant, ville = ri.VilleRestaurant, uniqueId=ri.UniqueId };
            }

            return orderedResults;
        }

        /** Retourne la liste des restos dans la ville spécifiée
         */
        public IEnumerable<Resto> RechercherRestosParVille(string in_ville)
        {
            var results = (from r in Restos
                           where (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_0") >= 0)
                           & (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_{") < 0)
                           select r) as IEnumerable<Resto>;

            var orderedResults = from r in results
                                 orderby r.nom
                                 select r;

            return orderedResults;
        }

        /** Retourne les villes commençant par
         */
        public string[] RechercherVillesDemarrantPar(string in_villePrefix)
        {
            
            //var results = (from r in Restos
            //               where (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_villePrefix)) >= 0)
            //               & (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_villePrefix) + "{") < 0)
            //               select r) as IEnumerable<Resto>;

            var results = RechercherRestosParMots(in_villePrefix);

            var villes = (from v in results
                          orderby v.ville ascending
                          select v.ville).Distinct();

            return villes.ToArray();
        }

        /** Retourne le premier resto dans la ville spécifié s'il existe, sinon retoure null
         */
        public Resto GetRestoParNomEtVille(string in_nom, string in_ville)
        {
            try
            {
                // StartWith is not supported => replace with a string comparison
                //var result = (from r in Restos
                //             where r.PartitionKey.StartsWith(StringHelper.RemoveAccentuation(in_ville)+"_") 
                //             select r) as IEnumerable<Resto>;

                // Retreive all the restaurants for a given town
                // Query PartitionKey : >= ville_0 and < ville_(character after 9 == ':')
                var result = (from r in Restos
                              where (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_0") >= 0)
                              & (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_:") < 0)
                              select r) as IEnumerable<Resto>;

                // Filter in_memory (because azure would not support the concatened query)
                var filtered = from s in result
                               where StringHelper.RemoveAccentuation(s.nom).Equals(StringHelper.RemoveAccentuation(in_nom))
                               select s;

                return filtered.FirstOrDefault();
            }
            catch (InvalidOperationException)
            {
                RoleManager.WriteToLog("Information", "RestoService, GetRestoParNomEtVille : Pas de restaurant nommé " + in_nom + " pour la ville " + in_ville);
                return null;
            }
        }

        /** Retourne tous les restos dans la ville spécifiée, et null si pas de restos
         */
        public IEnumerable<Resto> GetRestosParNomEtVille(string in_nom, string in_ville)
        {
            try
            {
                // Retreive all the restaurants for a given town
                // Query PartitionKey : >= ville_0 and < ville_(character after 9 == ':')
                var result = (from r in Restos
                              where (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_0") >= 0)
                              & (r.PartitionKey.CompareTo(StringHelper.RemoveAccentuation(in_ville) + "_:") < 0)
                              select r) as IEnumerable<Resto>;

                // Filter in_memory (because azure would not support the concatened query)
                var filtered = from s in result
                               where StringHelper.RemoveAccentuation(s.nom).Equals(StringHelper.RemoveAccentuation(in_nom))
                               select s;

                return result;
            }
            catch (InvalidOperationException)
            {
                RoleManager.WriteToLog("Information", "RestoService, GetRestoParNomEtVille : Pas de restaurant nommé " + in_nom + " pour la ville " + in_ville);
                return null;
            }
        }

        public Resto GetRestoByUniqueId(string in_uniqueID)
        {
            // [PENDING] Attention si on change la clé de partitionnement, la requête ne fonctionnera plus
            // Mais je ne veux pas utiliser l'unique id pour des raisons de perfs
            var result = (from r in Restos
                          where r.PartitionKey.Equals(Resto.ExtractPartitionKeyFromRestoUniqueId(in_uniqueID))
                          & r.RowKey.Equals(Resto.ExtractRowKeyFromRestoUniqueId(in_uniqueID))
                          select r) as IEnumerable<Resto>;

            // [QUALITE] Si on en trouve plusieurs, c'est qu'on a un pb de qualité dans le SGBDR
            switch (result.Count())
            {
                case 0: return null;
                case 1: return result.FirstOrDefault();
                default:
                    RoleManager.WriteToLog("Warning", "RestoService, GetRestoByUniqueId : Several restaurants with same unique id " + in_uniqueID);
                    return result.FirstOrDefault();
            }
        }

        /**
         */
        public Element CreerElement(string in_restoId, string in_element, string in_categorie, string in_description)
        {

            try
            {
                var l_created = new Element(in_restoId, in_element, ANONYMOUS, in_categorie, in_description);
                this.AddObject("Elements", l_created);
                this.SaveChanges();
                return l_created;
            }
            catch (DataServiceRequestException dsre)
            {
                //var log = log4net.LogManager.GetLogger(this.GetType());
                //log.Debug("AnniversaireService, CreerAnniversaire : Entry already exists !");
                RoleManager.WriteToLog("Information", "RestoService, CreerElement : Entry already exists !");

                return null;
            }
            catch (InvalidOperationException ioe)
            {
                // Pb d'implementation, on réutilise un contexte existant
                //var log = log4net.LogManager.GetLogger("Metier.AnniversaireService");
                //log.Debug("AnniversaireService, CreerAnniversaire : Pb d'implémentation !");
                RoleManager.WriteToLog("Information", "RestoService, CreerElement : Pb d'implémentation !");

                return null;
            }
        }

        // STEVE : je ne comprends pas à quoi sert cette méthode, elle devrait retourner une liste
        public Element GetElementParNomEtResto(string in_restoUniqueId, string elementName)
        {
            var result = from e in Elements
                         where (e.PartitionKey == in_restoUniqueId) &
                         (e.Title == elementName)
                         select e;

            return result == null ? null : ProcessElementQuery(result).FirstOrDefault();
        }

        /// <summary>
        /// Retourne la liste des restos qui commencent par la ville demandée
        /// </summary>
        /// <param name="in_restoId"></param>
        /// <returns></returns>
        public IEnumerable<Element> RechercherElementsPourResto(string in_restoId)
        {
            var found = from e in Elements
                        where (e.PartitionKey == in_restoId)
                        select e;

            return found;
        }

        /** Ajoute le prix constaté à la date du jour pour l'élément spécifié
        */
        public DetailPrix AjouterPrix(string in_elementID, double in_prixMaintenant)
        {

            try
            {
                var l_created = new DetailPrix(in_elementID, in_prixMaintenant, ANONYMOUS);
                this.AddObject("DetailPrix", l_created);
                this.SaveChanges();
                return l_created;
            }
            catch (DataServiceRequestException)
            {
                //var log = log4net.LogManager.GetLogger(this.GetType());
                //log.Debug("AnniversaireService, CreerAnniversaire : Entry already exists !");
                RoleManager.WriteToLog("Information", "RestoService, AjouterPrix : Entry already exists !");

                return null;
            }
            catch (InvalidOperationException)
            {
                // Pb d'implementation, on réutilise un contexte existant
                //var log = log4net.LogManager.GetLogger("Metier.AnniversaireService");
                //log.Debug("AnniversaireService, CreerAnniversaire : Pb d'implémentation !");
                RoleManager.WriteToLog("Information", "RestoService, AjouterPrix : Pb d'implémentation !");

                return null;
            }
        }


        /** Ajoute la modification de prix à la date du jour constaté
         * l'ancien prix correspond au 30 juin, 8:00 AM par choix arbitraire
         * Retourne le nouveau prix
         */
        public DetailPrix AjouterModificationDePrix(string in_elementID, double in_prixMaintenant, double in_prixAvant)
        {

            try
            {
                // Création du prix maintenant
                var l_nouveau = new DetailPrix(in_elementID, in_prixMaintenant, ANONYMOUS);
                this.AddObject("DetailPrix", l_nouveau);

                // Création du prix avant
                if (TICKS_AVANT_LA_BAISSE == 0)
                {
                    
                    var dateSaisie = DateTime.Parse("Tue, 30 Jun 2009 08:00:00 GMT");
                        TICKS_AVANT_LA_BAISSE = dateSaisie.Ticks;
                }
                var l_avant = new DetailPrix(in_elementID, in_prixAvant, ANONYMOUS);
                l_avant.RowKey = String.Format("{0:10}", TICKS_AVANT_LA_BAISSE);

                this.AddObject("DetailPrix", l_avant);

                this.SaveChanges();        

                return l_nouveau;
            }
            catch (DataServiceRequestException dsre)
            {
                //var log = log4net.LogManager.GetLogger(this.GetType());
                //log.Debug("AnniversaireService, CreerAnniversaire : Entry already exists !");
                RoleManager.WriteToLog("Information", "RestoService, AjouterModificationDePrix : Entry already exists !");

                return null;
            }
            catch (InvalidOperationException ioe)
            {
                // Pb d'implementation, on réutilise un contexte existant
                //var log = log4net.LogManager.GetLogger("Metier.AnniversaireService");
                //log.Debug("AnniversaireService, CreerAnniversaire : Pb d'implémentation !");
                RoleManager.WriteToLog("Information", "RestoService, AjouterModificationDePrix : Pb d'implémentation !");

                return null;
            }
        }
        /** Retourne la liste des prix pour l'élément spécifié
         */
        public IEnumerable<DetailPrix> RechercherPrixPourElement(string in_elementId)
        {
            var found = from e in DetailPrix
                        where (e.PartitionKey == in_elementId)
                        select e;

            return found;
        }

        /** Retourne la liste des prix pour l'élément spécifié
         */
        public DetailPrix RechercherDernierPrixPourElement(string in_elementId)
        {
            var found = from e in DetailPrix
                        where (e.PartitionKey == in_elementId)
                        select e;

            return found.AsEnumerable().LastOrDefault<DetailPrix>();

        }

        /** Retourne le prix moyen pour l'élément spécifié
       */
        public double RechercherPrixMoyenPourElement(string in_elementId)
        {
            var found = from e in DetailPrix
                        where (e.PartitionKey == in_elementId)
                        select e;
            if (found.AsEnumerable().Count() > 0)
            {
                return found.AsEnumerable().Average(prix => prix.prix);
            }
            else
                return 0;
        }

        public double RechercherPrixMoyenPourElementDuTitre(string titre)
        {
            var elements = from e in Elements
                           where (e.Title == titre)
                           select e;

            double moyenne = 0;
            int i = 0;
            foreach (var item in elements)
            {
                double prixmoyenparel = RechercherPrixMoyenPourElement(item.GetElementID());
                if (prixmoyenparel != 0)
                {
                    moyenne += prixmoyenparel;
                    i++;
                }
            }
            moyenne = moyenne / i;
            return moyenne;

        }


        public void SupprimerResto(Resto resto)
        {
            // [PENDING] Faire le nettoyage de façon asynchrone
            //foreach (Element element in RechercherElementsPourResto(resto.uniqueId))
            //{
            //    this.SupprimerElement(element);
            //}
            this.AttachTo("Restos", resto, "*");
            this.DeleteObject(resto);
            this.SaveChanges();
        }

        /// <summary>
        /// Supprime un element et tous ses prix associés
        /// </summary>
        /// <param name="element">L'element a supprime</param>
        public void SupprimerElement(Element element)
        {
            // [PENDING] Faire le nettoyage de façon asynchrone
            //foreach (DetailPrix prix in RechercherPrixPourElement(element.UniqueId))
            //{
            //    this.SupprimerDetailPrix(prix);
            //}

            this.AttachTo("Elements", element, "*");
            this.DeleteObject(element);
            this.SaveChanges();
        }

        public void SupprimerDetailPrix(DetailPrix prix)
        {
            this.AttachTo("DetailPrix", prix, "*");
            this.DeleteObject(prix);
            this.SaveChanges();
        }

        //public IEnumerable<Resto> ProcessRestoQuery(IQueryable<Resto> resultset)
        //{
        //    TableStorageDataServiceQuery<Resto> query = new TableStorageDataServiceQuery<Resto>(resultset as DataServiceQuery<Resto>);
        //    IEnumerable<Resto> queryResults = query.ExecuteAllWithRetries();
        //    return queryResults;
        //}

        public IEnumerable<Element> ProcessElementQuery(IQueryable<Element> resultset)
        {
            TableStorageDataServiceQuery<Element> query = new TableStorageDataServiceQuery<Element>(resultset as DataServiceQuery<Element>);
            IEnumerable<Element> queryResults = query.ExecuteAllWithRetries();
            return queryResults;
        }

        //public IEnumerable<string> ProcessStringQuery(IQueryable<string> resultset)
        //{
        //    TableStorageDataServiceQuery<string> query = new TableStorageDataServiceQuery<string>(resultset as DataServiceQuery<string>);
        //    IEnumerable<string> queryResults = query.ExecuteAllWithRetries();
        //    return queryResults;
        //}

        private void CreerEchantillons(Resto r)
        {
            CreerElement(r.uniqueId, "Menu enfant", "Menu", "Menu pour les enfants");
            CreerElement(r.uniqueId, "Menu midi entree plat ou plat dessert", "Menu", "Menu midi leger comprenant entree plat ou plat dessert");
            CreerElement(r.uniqueId, "Menu midi entre plat dessert", "Menu", "Menu midi comprenant entree plat et dessert");
            CreerElement(r.uniqueId, "Plat du jour 1", "Plat du jour", "Plat du jour d'un certain montant");
            CreerElement(r.uniqueId, "Plat du jour 2", "Plat du jour", "Plat du jour d'un certain autre montant");
            CreerElement(r.uniqueId, "Coca-cola", "Boisson", "Coca cola en bouteille 33cl");
            CreerElement(r.uniqueId, "Cafe expresso", "Boisson", "Cafe expresso");
        }

    }
}
